// checked 02/12/2018
// tested 02/13/2018
package com.walkerma.library;

public class Parity {

	public enum DisplayMode{CHAR, HEX}
	public enum ParitySort{NONE, XOR, ADD, CRC, CHECK_SUM}
	public enum EndMark{NONE, CR, CRLF}


	//=================================== Add ===================================//
	public static byte getAddByte(byte[] bIn){
		// input: {12, 34}  output:46
		int nResult=0;
		for (int i=0; i<bIn.length; i++){
			nResult += (int) bIn[i];
			nResult &= 0xff;
		}
		return (byte) nResult;
	}


	public static boolean checkAddByte(byte[] bIn){
		// input: {12, 34, 46}  output:true
		if(bIn == null) return false;
		int nLen = bIn.length;
		if(nLen < 2) return false;

		byte[] bBytesOriginal = new byte[nLen-1];

		for(int i=0; i<nLen-1; i++){
			bBytesOriginal[i] = bIn[i];
		}

		if (getAddByte(bBytesOriginal) == bIn[nLen-1])
			return true;
		else
			return false; 
	}


	public static String getAddHexChars(String strHexChars){
		// input: "1d3f"   output:"5C"
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		return ByteProcess.byteToTwoHexChars(
				getAddByte(bData), false);
	}


	public static boolean checkAddHexChars(String strHexChars){
		// input: "1d3f5c"   output:true
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		if(checkAddByte(bData))
			return true;
		else
			return false;
	}


	public static String getAddHexCharsFromString(String strData){
		// input:"#01"	output:"84"
		byte[] bData = ByteProcess.enStringToBytes(strData);
		return ByteProcess.byteToTwoHexChars(
				getAddByte(bData), false);
	}


	public static boolean checkAddHexCharsFromString(String strData){
		// input:"#0184"	output:true
		String strOriginalAddHexChars, strOriginalString;
		int nLen = strData.length();
		if (nLen < 3) return false;
		strOriginalAddHexChars = strData.substring(nLen - 2);
		strOriginalString = strData.substring(0, nLen-2);	   
		if(strOriginalAddHexChars.equals(
				getAddHexCharsFromString(strOriginalString)))
			return true;
		else
			return false;
	}


	//=================================== Xor ===================================//
	public static byte getXorByte(byte[] bIn){
		// input:{17, 51}	output:34
		byte bResult=0;
		for (int i=0; i<bIn.length; i++){
			bResult ^= bIn[i];
		}
		return bResult;
	}


	public static boolean checkXorByte(byte[] bIn){
		// input:{17, 51, 34}	output:true
		if (getXorByte(bIn) == 0)
			return true;
		else
			return false;
	}


	public static String getXorHexChars(String strHexChars){
		// input:"1122"		output:"33"
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		return ByteProcess.byteToTwoHexChars(
				getXorByte(bData), false);
	}


	public static boolean checkXorHexChars(String strHexChars){
		// input:"112233"		output:true
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		if(getXorByte(bData) == 0)
			return true;
		else
			return false;
	}


	public static String getXorHexCharsFromString(String strData){
		// input:"#01"		output:"22"
		byte[] bData = ByteProcess.enStringToBytes(strData);
		return ByteProcess.byteToTwoHexChars(
				getXorByte(bData), false);
	}


	public static boolean checkXorHexCharsFromString(String strData){
		// input:"#0122"		output:true
		String strOriginalXorHexChars, strOriginalString;
		int nLen = strData.length();
		if (nLen < 3) return false;
		strOriginalXorHexChars = strData.substring(nLen - 2);
		strOriginalString = strData.substring(0, nLen-2);	   
		if(strOriginalXorHexChars.equals(
				getXorHexCharsFromString(strOriginalString)))
			return true;
		else
			return false;
	}


	//=================================== CRC ===================================//
	public static int getCrcByte(byte[] bIn){
		//input:{0xFF, 3, 0xFC, 1, 0, 0x16 }		output:0xB04A
		int udWord_crc = 0xffff;
		final int udWord_Const = 0xa001;
		for(int i=0; i<bIn.length; i++)
		{
			udWord_crc ^= ((int)bIn[i]) & 0xff;//unsigned
			for(int j=0; j<=7; j++)
			{
				if(1 == (udWord_crc & 1))
				{
					udWord_crc >>= 1;
					udWord_crc ^= udWord_Const;
				}
				else{
					udWord_crc >>= 1;
				}
			}
		}
		return udWord_crc & 0xffff;
	}


	public static boolean checkCrcByte(byte[] bIn){
		//input:{0xFF, 3, 0xFC, 1, 0, 0x16, 0xB0, 0x4A}		output:true
		if (getCrcByte(bIn) == 0)
			return true;
		else
			return false;
	}


	public static String getCrcHexChars(String strHexChars){
		//input:"FF03FC010016"		output:"B04A"
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		return ByteProcess.wordToFourHexChars(getCrcByte(bData), false);
	}


	public static boolean checkCrcHexChars(String strHexChars){
		//input:"FF03FC010016B04A"		output:true
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		if(getCrcByte(bData) == 0)
			return true;
		else
			return false;
	}


	public static String getCrcHexCharsFromString(String strData){
		//input:"#01"		output:"55DE"
		byte[] bData = ByteProcess.enStringToBytes(strData);
		return ByteProcess.wordToFourHexChars(getCrcByte(bData), false);
	}


	public static boolean checkCrcHexCharsFromString(String strData){
		//input:"#0155DE"		output:true
		String strOriginalCrcHexChars, strOriginalString;
		int nLen = strData.length();
		if (nLen < 6) return false;
		strOriginalCrcHexChars = strData.substring(nLen - 4);
		strOriginalString = strData.substring(0, nLen-4);	   
		if(strOriginalCrcHexChars.equals(
				getCrcHexCharsFromString(strOriginalString)))
			return true;
		else
			return false;
	}


	//=================================== CheckSum ===================================//
	public static int getCheckSumByte(byte[] bIn){
		//input:{0x1d, 0x3a, 0x56, 0x2c}		output:0x8c99
		int nLen = bIn.length;
		int udLow, udHigh;
		int udWord_Sum = 0;
		if(nLen%2==1) return 0; //for even bytes only
		for (int i=0; i<nLen/2; i++){
			udLow = ((int)bIn[i*2]) & 0xff;	//unsigned process
			udHigh = ((int)bIn[i*2+1]) & 0xff;
			udWord_Sum += udLow + udHigh * 256;
		}
		udLow = udWord_Sum & 0xffff;
		udHigh = (udWord_Sum >> 16) & 0xffff;
		udWord_Sum = udLow + udHigh;
		return (~udWord_Sum) & 0xffff;
	}


	public static boolean checkCheckSumByte(byte[] bIn){
		//input:{0x1d, 0x3a, 0x56, 0x2c, 0x8c, 0x99}		output:true
		if (getCheckSumByte(bIn) == 0)
			return true;
		else
			return false;
	}


	public static String getCheckSumHexChars(String strHexChars){
		//input:"1d3a562c"		output:"8C99"
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		return ByteProcess.wordToFourHexChars(
				getCheckSumByte(bData), false);
	}


	public static boolean checkCheckSumHexChars(String strHexChars){
		//input:"1d3a562c8C99"		output:true
		byte[] bData = ByteProcess.hexCharsToBytes(strHexChars);
		if(getCheckSumByte(bData) == 0)
			return true;
		else
			return false;
	}


	public static String getCheckSumHexCharsFromString(String strData){
		//input:"#012"		output:AB9D
		byte[] bData = ByteProcess.enStringToBytes(strData);
		return ByteProcess.wordToFourHexChars(
				getCheckSumByte(bData), false);
	}


	public static boolean checkCheckSumHexCharsFromString(String strData){
		//input:"#012AB9D"		output:true
		String strOriginalCheckSumHexChars, strOriginalString;
		int nLen = strData.length();
		if (nLen < 6) return false;
		strOriginalCheckSumHexChars = strData.substring(nLen - 4);
		strOriginalString = strData.substring(0, nLen-4);	   
		if(strOriginalCheckSumHexChars.equals(
				getCheckSumHexCharsFromString(strOriginalString)))
			return true;
		else
			return false;
	}



	//=================================== EndMark(String) ===================================//
	public static String addEndMark(String strData, 
			EndMark nEndMark, DisplayMode nDisplayMode){
		String strResult = strData;
		if(nDisplayMode == DisplayMode.HEX) 
			strResult = ByteProcess.normalizeHexChars(strData);		
		switch(nEndMark){
		case CR:
			if(nDisplayMode == DisplayMode.HEX)
				return strResult + "0D";
			else
				return strResult + "\r";
		case CRLF:
			if(nDisplayMode == DisplayMode.HEX)
				return strResult + "0D0A";
			else
				return strResult + "\r\n";
		default:
			break;
		}		
		return strResult;
	}


	public static boolean checkEndMark(String strData, 
			EndMark nEndMark, DisplayMode nDisplayMode){
		String strData_Regular = strData;
		int nLen;	
		if(nDisplayMode == DisplayMode.HEX) 
			strData_Regular = ByteProcess.normalizeHexChars(strData);
		nLen = strData_Regular.length();		
		switch(nEndMark){
		case CR:
			if(nDisplayMode == DisplayMode.HEX){
				if(nLen < 2) return false;
				if(strData_Regular.endsWith("0D"))
					return true;
				else
					return false;
			}
			else{
				if(nLen < 1) return false;
				if(strData_Regular.endsWith("\r"))
					return true;
				else
					return false;
			}
		case CRLF:
			if(nDisplayMode == DisplayMode.HEX){
				if(nLen < 4) return false;
				if(strData_Regular.endsWith("0D0A"))
					return true;
				else
					return false;
			}
			else{
				if(nLen < 2) return false;
				if(strData_Regular.endsWith("\r\n"))
					return true;
				else
					return false;
			}
		default:
			break;
		}		
		return true; //ENDMARK_NONE
	}


	public static String deleteEndMark(String strData, 
			EndMark nEndMark, DisplayMode nDisplayMode){
		String strData_Regular = strData;
		if(nDisplayMode == DisplayMode.HEX) 
			strData_Regular = ByteProcess.normalizeHexChars(strData);
		if(checkEndMark(strData_Regular, nEndMark, 
				nDisplayMode) == false)
			return null;

		switch(nEndMark){
		case CR:
			if(nDisplayMode == DisplayMode.HEX)
				strData_Regular = strData_Regular.substring(0, 
						strData_Regular.length() - 2);
			else
				strData_Regular = strData_Regular.substring(0, 
						strData_Regular.length() - 1);
			break;
		case CRLF:
			if(nDisplayMode == DisplayMode.HEX)
				strData_Regular = strData_Regular.substring(0, 
						strData_Regular.length() - 4);
			else
				strData_Regular = strData_Regular.substring(0, 
						strData_Regular.length() - 2);
		default:
			break;
		}
		return strData_Regular;
	}


	//=================================== Package(String) ===================================//
	public static boolean checkPackageWithoutEndMark(String strData, 
			ParitySort nCheckMode, DisplayMode nDisplayMode){
		String strData_Regular = strData;
		int nLen;	
		if(nDisplayMode == DisplayMode.HEX) 
			strData_Regular = ByteProcess.normalizeHexChars(strData);
		nLen = strData_Regular.length();		
		switch(nCheckMode){
		case XOR:
			if(nDisplayMode == DisplayMode.HEX){
				// 112233
				if(nLen < 4) return false;
				return checkXorHexChars(strData_Regular);
			}
			else{
				// #01[22]
				if(nLen < 3) return false;
				return checkXorHexCharsFromString(strData_Regular);
			}
		case ADD:
			if(nDisplayMode == DisplayMode.HEX){
				// 112233
				if(nLen < 4) return false;
				return checkAddHexChars(strData_Regular);
			}
			else{
				// #01[84]
				if(nLen < 3) return false;
				return checkAddHexCharsFromString(strData_Regular);
			}
		case CRC:
			if(nDisplayMode == DisplayMode.HEX){
				// FF 03 FC 01 00 16 B04A
				if(nLen < 6) return false;
				return checkCrcHexChars(strData_Regular);
			}
			else{
				if(nLen < 5) return false;
				return checkCrcHexCharsFromString(strData_Regular);
			}
		case CHECK_SUM:
			if(nDisplayMode == DisplayMode.HEX){
				// 45 c0 00 40 fd 54 00 00 01 59 1d 3a ac 10 12 01 e0 00 00 05 -> 1d 3a
				if(nLen < 8) return false;
				return checkCheckSumHexChars(strData_Regular);
			}
			else{
				if(nLen < 6) return false;
				return checkCheckSumHexCharsFromString(strData_Regular);
			}		
		default:
			break;
		}     
		return true;  //CHK_NONE
	}


	public static String getEntirePackage(String strData, 
			ParitySort nCheckMode, EndMark nEndMark, 
			DisplayMode nDisplayMode){
		String strEntirePackage = strData;
		if(nDisplayMode == DisplayMode.HEX)
			strEntirePackage = ByteProcess.normalizeHexChars(strData);	
		switch(nCheckMode){
		case XOR:
			if(nDisplayMode == DisplayMode.HEX)
				strEntirePackage += getXorHexChars(
						strEntirePackage);
			else
				strEntirePackage += getXorHexCharsFromString(
						strEntirePackage);
			break;
		case ADD:
			if(nDisplayMode == DisplayMode.HEX)
				strEntirePackage += getAddHexChars(
						strEntirePackage);
			else
				strEntirePackage += 
				getAddHexCharsFromString(strEntirePackage);
			break;
		case CRC:
			if(nDisplayMode == DisplayMode.HEX)
				strEntirePackage += getCrcHexChars(
						strEntirePackage);
			else
				strEntirePackage += 
				getCrcHexCharsFromString(strEntirePackage);
			break;
		case CHECK_SUM:
			if(nDisplayMode == DisplayMode.HEX)
				strEntirePackage += 
				getCheckSumHexChars(strEntirePackage);
			else
				strEntirePackage += 
				getCheckSumHexCharsFromString(strEntirePackage);
			break;			
		default:
			break;
		}
		return addEndMark(strEntirePackage, nEndMark, nDisplayMode);
	}


	public static boolean checkEntirePackage(String strData, 
			ParitySort nCheckMode, EndMark nEndMark, 
			DisplayMode nDisplayMode){
		String strMainPackage = deleteEndMark(strData, 
				nEndMark, nDisplayMode);
		if(strMainPackage == null) return false;
		return checkPackageWithoutEndMark(strMainPackage, 
				nCheckMode, nDisplayMode);
	}


	public static String pickPurePackage(String strData, 
			ParitySort nCheckMode, EndMark nEndMark, 
			DisplayMode nDisplayMode){
		if(!checkEntirePackage(strData, nCheckMode, 
				nEndMark, nDisplayMode)) return null;
		String strMainPackage = deleteEndMark(strData, 
				nEndMark, nDisplayMode);
		if(strMainPackage == null) return null;    
		int nLen = strMainPackage.length();

		switch(nCheckMode){
		case XOR:
		case ADD:
			return strMainPackage.substring(0, nLen - 2);
		case CRC:
		case CHECK_SUM:
			return strMainPackage.substring(0, nLen - 4);
		default:
			return strMainPackage; //CHK_NONE
		}
	}	
}


